<template>
  <div ref="canvasContainer" class="canvas-container"></div>
</template>

<script>
  import * as THREE from "three";

  //引入性能监视器stats.js
  import Stats from 'three/addons/libs/stats.module.js';

  import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
  // 添加CSS2D渲染器
  import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

  import imgUrl1 from '@/assets/img/5.jpg';

  import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

  import { Line2 } from 'three/examples/jsm/lines/Line2';
  import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial';
  import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry';

  export default {
    name: "几何体BufferGeometry",
    components: {
    },
    computed: {},
    data() {
      return {};
    },
    mounted() {
      // 初始化场景、相机、渲染器等
      this.initScene();
      this.createLight()

      // this.createGeometry()

      this.createHouse()

      this.animate();
      this.createControls()
      // 窗口大小自适应
      window.addEventListener('resize', this.onWindowResize)
    },
    methods: {
      // -几何体顶点位置数据和点模型
      // -线模型对象
      createGeometry() {
        // 1. 创建点
        // const geometry = new THREE.BufferGeometry();
        // // 创建一个 Float32Array 数组，用于存储顶点位置数据。
        // // 这里我们创建了一个包含 3 个顶点的数组，每个顶点有 3 个分量（x, y, z）。
        // const vertices = new Float32Array([
        //   0, 0, 0, // 顶点1
        //   10, 0, 0, // 顶点2
        //   0, 10, 0 // 顶点3
        // ]);
        // // 使用 setAttribute 方法将顶点位置数据添加到 BufferGeometry 中。
        // // 第一个参数 'position' 是顶点位置属性的名称，
        // // 第二个参数 new THREE.BufferAttribute(vertices, 3) 是一个 BufferAttribute 对象，
        // // 它包含了顶点位置数据和每个顶点的分量数（这里是 3，因为每个顶点有 x, y, z 三个分量）。
        // geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
        // // 创建一个 BasicMaterial 材质对象，用于渲染点。
        // const material = new THREE.PointsMaterial({
        //   color: 0x00ff00, // 设置点的颜色为绿色。
        //   size: 20, // 设置点的大小为 10 像素。
        //   sizeAttenuation: false // 禁用点的大小衰减，使所有点的大小相同。
        // });
        // // 创建一个 Points 对象，将 BufferGeometry 和 PointsMaterial 组合在一起
        // const points = new THREE.Points(geometry, material);
        // this.scene.add(points);

        // 2. 创建线
        // const points = [
        //   0, 0, 0,    // 顶点1
        //   50, 0, 0,   // 顶点2
        //   0, 100, 0,  // 顶点3
        //   0, 0, 10,   // 顶点4
        //   0, 0, 100,  // 顶点5
        //   50, 0, 10   // 顶点6
        // ];
        // const geometry = new LineGeometry();
        // geometry.setPositions(points);
        // const material = new LineMaterial({
        //   color: 0xff0000,
        //   linewidth: 20, // 这里设置线宽
        //   resolution: new THREE.Vector2(window.innerWidth, window.innerHeight)
        // });
        // const line = new Line2(geometry, material);
        // this.scene.add(line);


        // 3.创建矩形平面
        // const vertices = new Float32Array([
        //   0, 0, 0, //顶点1坐标
        //   30, 0, 0, //顶点2坐标
        //   30, 30, 0, //顶点3坐标

        //   0, 0, 0, //顶点4坐标   和顶点1位置相同
        //   30, 30, 0, //顶点5坐标  和顶点3位置相同
        //   0, 30, 0, //顶点6坐标
        // ])
        // const geometry = new THREE.BufferGeometry();
        // geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
        // const material = new THREE.MeshBasicMaterial({
        //   color: 0xff0000,
        // });
        // const cube = new THREE.Mesh(geometry, material);
        // this.scene.add(cube);

        // 4.几何体顶点索引数据
        // const vertices = new Float32Array([
        //   0, 0, 0,   // 顶点0
        //   30, 0, 0,  // 顶点1
        //   30, 30, 0, // 顶点2
        //   0, 30, 0   // 顶点3
        // ]);
        // // 索引数据 - 定义两个三角形组成矩形
        // const indices = new Uint16Array([
        //   0, 1, 2,  // 第一个三角形
        //   0, 2, 3   // 第二个三角形
        // ]);
        // const geometry = new THREE.BufferGeometry();
        // geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
        // geometry.setIndex(new THREE.BufferAttribute(indices, 1));

        // const material = new THREE.MeshBasicMaterial({
        //   color: 0xcc66cc,
        //   side: THREE.DoubleSide
        // });

        // const plane = new THREE.Mesh(geometry, material);
        // this.scene.add(plane);

        // 5.顶点法线数据
        const vertices = new Float32Array([
          0, 0, 0,   // 顶点0
          30, 0, 0,  // 顶点1
          30, 30, 0, // 顶点2
          0, 30, 0   // 顶点3
        ]);
        const indices = new Uint16Array([
          0, 1, 2,  // 第一个三角形
          0, 2, 3   // 第二个三角形
        ]);
        const geometry = new THREE.BufferGeometry();
        geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
        geometry.setIndex(new THREE.BufferAttribute(indices, 1));
        // 定义顶点法线数据
        const normals = new Float32Array([
          0, 0, 1,   // 顶点0法线
          0, 0, 1,   // 顶点1法线
          0, 0, 1,   // 顶点2法线
          0, 0, 1    // 顶点3法线
        ]);
        geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
        const material = new THREE.MeshPhongMaterial({
          color: 0xcc66cc,
          side: THREE.DoubleSide,
          specular: 0xffffff,  // 添加高光颜色
          shininess: 100       // 增加高光强度
        });
        const plane = new THREE.Mesh(geometry, material);
        // this.scene.add(plane);

        const plane2 = plane.clone()
        plane2.position.set(0, 0, 30)
        // 改变clone的模型的对象的颜色，会影响到原对象的颜色
        plane2.material.color = new THREE.Color(0xcccccc)


        // this.scene.add(plane2)

        const group = new THREE.Group()
        group.add(plane)
        group.add(plane2)

        // 一个group里的模型会有共同的旋转、缩放、平移
        group.rotation.x = - Math.PI / 4

        this.scene.add(group)

        console.log('查看Scene的子对象', this.scene.children);
      },
      createHouse() {
        // 批量创建多个长方体表示高层楼
        const group1 = new THREE.Group(); //所有高层楼的父对象
        group1.name = "高层";
        for (let i = 0; i < 5; i++) {
          const geometry = new THREE.BoxGeometry(20, 60, 10);
          const material = new THREE.MeshLambertMaterial({
            color: 0x00ffff
          });
          const mesh = new THREE.Mesh(geometry, material);
          mesh.position.x = i * 30; // 网格模型mesh沿着x轴方向阵列
          group1.add(mesh); //添加到组对象group1
          mesh.name = i + 1 + '号楼';
          // console.log('mesh.name',mesh.name);
        }
        group1.position.y = 30;


        const group2 = new THREE.Group();
        group2.name = "洋房";
        // 批量创建多个长方体表示洋房
        for (let i = 0; i < 5; i++) {
          const geometry = new THREE.BoxGeometry(20, 30, 10);
          const material = new THREE.MeshLambertMaterial({
            color: 0x00ffff
          });
          const mesh = new THREE.Mesh(geometry, material);
          mesh.position.x = i * 30;
          group2.add(mesh); //添加到组对象group2
          mesh.name = i + 6 + '号楼';
        }
        group2.position.z = 50;
        group2.position.y = 15;

        const model = new THREE.Group();
        model.name = '小区房子';
        model.add(group1, group2);
        model.position.set(-50, 0, -25);

        model.traverse((obj) => {
          // obj.isMesh：if判断模型对象obj是不是网格模型'Mesh'
          if (obj.name == '4号楼') {//判断条件也可以是obj.type === 'Mesh'
            obj.material.color.set(0xffff00);

            const worldPosition = new THREE.Vector3();
            // 获取mesh的世界坐标，你会发现mesh的世界坐标受到父对象group的.position影响
            obj.getWorldPosition(worldPosition);
            console.log('世界坐标', worldPosition);

            // 3. 从父级移除并释放资源
            // 先标记为不可见
            obj.visible = false;

            // 延迟释放资源
            requestAnimationFrame(() => {
              if (obj.parent) {
                obj.parent.remove(obj);
              }
              if (obj.geometry) {
                obj.geometry.dispose();
              }
              if (obj.material) {
                obj.material.dispose();
              }
            });

          }
        });
        this.scene.add(model);
      },
      initScene() {
        // 1. 创建场景
        this.scene = new THREE.Scene()
        // 设置场景背景色
        this.scene.background = new THREE.Color(0x27304D);

        // 2. 创建相机（透视相机）
        this.camera = new THREE.PerspectiveCamera(
          75,  // 1. 视野角度(FOV - Field of View)- 单位是度(°)，表示相机垂直方向的视野范围。值越大，看到的场景越多(类似广角镜头)；值越小，看到的场景越少(类似长焦镜头)。通常设置在45-75度之间。
          window.innerWidth / window.innerHeight, // 2. 宽高比(aspect ratio)- 通常设置为渲染区域的宽度除以高度(如窗口宽高比)。这个值影响水平方向的视野。
          0.1, // 3. 近裁剪面(near clipping plane)- 相机能看到的最近距离。小于这个距离的物体不会被渲染。通常设置为一个较小的正数(如0.1)。
          1000 // 4. 远裁剪面(far clipping plane) - 相机能看到的最远距离。大于这个距离的物体不会被渲染。根据场景大小设置适当的值。
        );
        this.camera.position.set(30, 30, 30)
        // lookAt()方法可以让相机指向一个特定的点，它会受OrbitControls影响
        // this.camera.lookAt(5, 0, 0);

        // 3. 创建渲染器
        this.renderer = new THREE.WebGLRenderer({
          antialias: true,
          alpha: true
        });
        this.renderer.shadowMap.enabled = true;
        // 阴影类型对比 ：
        // - THREE.BasicShadowMap - 基础阴影，性能最好但边缘锯齿明显
        // - THREE.PCFShadowMap - 使用百分比渐近过滤(PCF)，边缘更平滑
        // - THREE.PCFSoftShadowMap - (当前使用的)在PCF基础上进一步柔化边缘，效果最自然但性能消耗最大
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        this.renderer.setSize(window.innerWidth, window.innerHeight)

        // 注意你的硬件设备设备像素比window.devicePixelRatio刚好是1，那么是否执行.setPixelRatio()不会有明显差异，
        // 不过为了适应不同的硬件设备屏幕，通常需要执行该方法。
        this.renderer.setPixelRatio(window.devicePixelRatio);

        // this.renderer.setClearColor(0x27304D, 1); //设置背景颜色  

        // 4. 挂载到DOM
        this.$refs.canvasContainer.appendChild(this.renderer.domElement)




        // 创建CSS2D渲染器
        this.labelRenderer = new CSS2DRenderer();
        this.labelRenderer.setSize(window.innerWidth, window.innerHeight);
        this.labelRenderer.domElement.style.position = 'absolute';
        this.labelRenderer.domElement.style.top = '0';
        this.labelRenderer.domElement.style.pointerEvents = 'none';
        this.$refs.canvasContainer.appendChild(this.labelRenderer.domElement);

        // 创建区域辅助线坐标系，X(红色)、Y(绿色)、Z(蓝色)
        const axesHelper = new THREE.AxesHelper(50);
        axesHelper.position.set(0, 0, 0);
        this.scene.add(axesHelper);

        // 初始化性能监视器
        this.stats = new Stats();
        this.stats.domElement.style.position = 'absolute';
        this.stats.domElement.style.left = '0px';
        this.stats.domElement.style.top = '0px';
        this.$refs.canvasContainer.appendChild(this.stats.domElement);
      },
      // 创建光源
      createLight() {
        // 点光源
        // const pointLight = new THREE.PointLight(0xffffff, 10000);
        // pointLight.position.set(30, 30, 30);
        // pointLight.castShadow = true;
        // this.scene.add(pointLight);

        // 点光源辅助观察
        // const pointLightHelper = new THREE.PointLightHelper(pointLight, 10);
        // this.scene.add(pointLightHelper);

        // 添加环境光
        const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
        this.scene.add(ambientLight);

        //增强平行光强度和添加辅助光
        const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
        directionalLight.position.set(0, 0, 30);
        directionalLight.castShadow = true;
        this.scene.add(directionalLight);

        // 平行光辅助观察
        // const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5, 0xff0000);
        // this.scene.add(dirLightHelper);

        // 添加第二个辅助平行光
        const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.5);
        directionalLight2.position.set(-10, 20, -10);
        this.scene.add(directionalLight2);

        // 添加半球光模拟环境反射
        const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6);
        hemisphereLight.position.set(0, 20, 0);
        this.scene.add(hemisphereLight);

        // 创建带高光的立方体
        // const geometry = new THREE.BoxGeometry(20, 10, 10);
        // const material = new THREE.MeshPhongMaterial({
        //   color: 0x00ffff,
        //   specular: 0xffffff, // 设置高光颜色
        //   shininess: 30, // 设置高光强度
        // });
        // const cube = new THREE.Mesh(geometry, material);
        // cube.position.set(0, 0, 0);
        // this.scene.add(cube);

      },
      // 动画循环
      animate() {
        requestAnimationFrame(this.animate)

        // 性能监视器
        this.stats.update();

        // 沿x轴旋转
        // if (this.cube) {
        //   this.cube.rotation.x += 0.01
        // }

        // 渲染循环
        this.renderer.render(this.scene, this.camera)
        this.labelRenderer.render(this.scene, this.camera)
      },
      onWindowResize() {

        this.camera.aspect = window.innerWidth / window.innerHeight
        // 更新投影矩阵
        this.camera.updateProjectionMatrix()
        this.renderer.setSize(window.innerWidth, window.innerHeight)
      },
      // 相机控件
      createControls() {
        this.controls = new OrbitControls(this.camera, this.renderer.domElement)
        // 增加平移速度
        this.controls.panSpeed = 2.0;
        // 增加缩放速度
        this.controls.zoomSpeed = 1.2;
        // 增加旋转速度
        this.controls.rotateSpeed = 1.0;

        // 禁用阻尼效果
        this.controls.enableDamping = false;
        // 禁用惯性效果
        this.controls.enableInertia = false;

        // 设置平移限制
        this.controls.maxPolarAngle = Math.PI; // 允许完全垂直旋转
        this.controls.minAzimuthAngle = -Infinity;
        this.controls.maxAzimuthAngle = Infinity;

        // this.controls.target.set(10, 0, 10);
        // this.controls.update();//update()函数内会执行camera.lookAt(controls.targe)

        // 设置了渲染循环,相机控件OrbitControls就不用再通过事件change执行renderer.render(scene, camera);
        // this.controls.addEventListener('change', () => {
        //   this.renderer.render(this.scene, this.camera)
        // })
      }
    },
    unmounted() {
      window.removeEventListener('resize', this.onWindowResize)
      // if (this.renderer) {
      //   this.renderer.dispose()
      //   this.$refs.canvasContainer.removeChild(this.renderer.domElement)
      // }
    }
  };
</script>

<style lang="scss">
  * {
    margin: 0;
    padding: 0;
  }
</style>